package br.edu.unifei.mestrado.kl;

import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import br.edu.unifei.mestrado.commons.graph.NodeWrapper;
import br.edu.unifei.mestrado.commons.partition.BestPartition;
import br.edu.unifei.mestrado.kl.util.DArray;
import br.edu.unifei.mestrado.kl.util.PairGain;
import br.edu.unifei.mestrado.kl.util.Partition;

public abstract class BKL extends KL {

	private Logger logger = LoggerFactory.getLogger(this.getClass());

	private Partition basePartition = null;

	/**
	 * UTIL: Inclui os nós adjacentes de pair.i e pair.j devido sua troca, pois eles tornaram fronteira. E
	 * depois executa o calculo dos custos normalmente, usando {@link KL#recalculateCost(PairGain, DArray)}.
	 * Tem que ser nessa ordem pq os nós incluídos na fronteira precisam ter seus valores de D calculados,
	 * devido a troca do par.
	 * 
	 */
	@Override
	protected void recalculateCost(PairGain pair, DArray dValues) {

		List<NodeWrapper> justIncludedI = basePartition.includeNodesToFrontier(pair.getI());
		List<NodeWrapper> justIncludedJ = basePartition.includeNodesToFrontier(pair.getJ());

		// calcula o D para os vizinhos de I e J
		for (NodeWrapper node : justIncludedI) {
			calculateDForNode(node, dValues);
		}
		for (NodeWrapper node : justIncludedJ) {
			calculateDForNode(node, dValues);
		}

		if ((justIncludedI.size() + justIncludedJ.size()) > 0) {
			logger.info("Quantidade de nós incluidos na fronteira: "
					+ (justIncludedI.size() + justIncludedJ.size()));
		}
		basePartition.printPartitions();

		super.recalculateCost(pair, dValues);
	}

	/*
	 * Metodo BKL.
	 */
	public BestPartition executeBKL(int level/* , Iterable<RelationshipWrapper> edgeCut */) {
		logger.warn("Executando BKL.");
		long delta = System.currentTimeMillis();

		// TODO: testar esse generateBoundaryPartition para ver se não dá efeito colateral em outro lugar

		long deltaGenPart = System.currentTimeMillis();
		basePartition = new Partition(level, getPartitionIndex());
		basePartition.generateBoundaryPartition(getGrafo().getAllEdges());

		// TODO: não precisa mais, pq o código da frontier foi pro Partition
		// //UTIL: precisa incluir os nós iniciais na fronteira também
		// frontier.includeInitialFrontier(basePartition.getAllNodes());

		deltaGenPart = System.currentTimeMillis() - delta;
		logger.warn("Tempo gasto para BKL gerar boundary partition: " + deltaGenPart + " ms");

		// UTIL: não pode ser fixed, pq já está particionado vindo do KL
		// basePartition.initialFixedPartition(grafoM_1.getAllNodes());

		int bestCut = basePartition.getCutWeight();
		logger.warn("Corte inicial BKL: " + bestCut);

		BestPartition bestPartition = basePartition.createBestPartition();

		int it = 0;
		int cut = 0;
		int bestIt = 0;
		for (it = 0; it < 10; ++it) {
			getGrafo().unlockAllNodes();// TODO: unlock somente da partition
			cut = klIteration(it, basePartition);

			// se não houve trocas
			if (cut == -1) {
				// se não houve melhoras, então não continua
				break;
			} else {
				// usa a mesma partição atual, como a partição inicial do passo seguinte
				if (cut <= bestCut) {
					// armazena o melhor resultado
					bestIt = it;
					bestPartition = basePartition.createBestPartition();
					bestCut = cut;
				}
			}
			logger.warn("Melhor corte: " + bestCut + " Iteração " + bestIt);
		}
		// logger.warn("End of partitioning.");
		logger.warn("Fim do BKL");

		delta = System.currentTimeMillis() - delta;
		logger.warn("Tempo gasto no BKL :" + delta + " ms");
		return bestPartition;
	}

	/**
	 * O BKL não aceita ganhos negativos.
	 */
	@Override
	protected boolean acceptNegativeGain() {
		return false;
	}
}
